/*---------------------------------------------------------------------------------*/
//ORGANIZATION:
//AUTHOR: bpeng(bogers.peng@gmail.com)
//SUMMARY:
/*---------------------------------------------------------------------------------*/
#include "../Buffer.h"
#include "../Exceptions.h"

using namespace Native::System;

size_t CBuffer::CalculateCapacity(size_t request_capacity, size_t unit_capacity)
{
    if (request_capacity <= unit_capacity) {
        return  unit_capacity;
    } else {
        if (request_capacity%unit_capacity == 0) {
            return request_capacity;
        } else {
            return ((int32_t)(request_capacity / unit_capacity) + 1) * unit_capacity;
        }
    }
}

CBuffer::CBuffer() throw() :
    _capacity(DEFAULT_UNIT_CAPACITY),
    _unit_capacity(DEFAULT_UNIT_CAPACITY),
    _import_rule(BUFFER_IMPORT_RULE_EXTEND),
    _cursor(0),
    _data(nullptr)
{
    try {
        _Allocate(_capacity);
        Clear();
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"CBuffer", e);
    }
}

CBuffer::CBuffer(const CBuffer& that) throw() :
    CObject(that),
    _capacity(that._capacity),
    _unit_capacity(that._unit_capacity),
    _import_rule(that._import_rule),
    _data(nullptr)
{
    try {
        _Allocate(_capacity);
        Clear();

        if (that.Usage() > 0) {
            memcpy(_data, that.Pointer(), that.Usage());
            _cursor += that.Usage();
        }
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"CBuffer", e);
    }
}

CBuffer::CBuffer(CBuffer&& that) :
    CObject(that),
    _capacity(that._capacity),
    _unit_capacity(that._unit_capacity),
    _import_rule(that._import_rule),
    _cursor(that._cursor),
    _data(that._data)
{
    that._Finalize();
}

CBuffer::CBuffer(size_t initialCapacity, size_t unit_capacity, EBufferImportRule import_rule) throw() :
    _unit_capacity(unit_capacity),
    _import_rule(import_rule),
    _cursor(0),
    _data(nullptr)
{
    try {
        _Allocate(initialCapacity);
        Clear();
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"CBuffer", e);
    }
}

CBuffer::~CBuffer()
{
    _Deallocate();
}

void CBuffer::_Finalize()
{
    _data = nullptr;
}

bool CBuffer::operator==(C_BUFFER& rvalue) const
{
    if(nullptr == _data || nullptr == rvalue._data) {
        return false;
    }

    if (_capacity == rvalue._capacity &&
        _unit_capacity == rvalue._unit_capacity &&
        _import_rule == rvalue._import_rule) {
            int c = memcmp(_data, rvalue._data, _capacity);
            return c == 0;
    } else {
        return false;
    }
}

bool CBuffer::operator!=(C_BUFFER& rvalue) const
{
    return !operator==(rvalue);
}

size_t CBuffer::_Allocate(size_t request_capacity) throw()
{
    CZeroValueException<size_t>::Check(NATIVE_SCENE(L"_Allocate"), request_capacity, L"request_capacity");

    _capacity = CalculateCapacity(request_capacity, _unit_capacity);
    _data = new byte_t[_capacity];
    _cursor = 0;

    return _capacity;
}

size_t CBuffer::_Reallocte(size_t request_capacity) throw()
{
    CZeroValueException<size_t>::Check(NATIVE_SCENE(L"_Reallocte"), request_capacity, L"request_capacity");

    _capacity = CalculateCapacity(request_capacity, _unit_capacity);
    delete[] _data;
    _data = nullptr;
    _data = new byte_t[_capacity];
    _cursor = 0;

    return _capacity;
}

void CBuffer::_Deallocate()
{
    if (nullptr != _data) {
        delete[] _data;
        _data = nullptr;
    }
}

size_t CBuffer::Usage() const
{
    return _cursor;
}

size_t CBuffer::Remain() const
{
    return Capacity() - Usage();
}

size_t CBuffer::Capacity() const
{
    return _capacity;
}

void CBuffer::Clear()
{
    memset(_data, 0, _capacity);
    _cursor = 0;
}

size_t CBuffer::Import(pointer_t const source, size_t source_capacity) throw()
{
    CNullPointerException::Check(NATIVE_SCENE(L"Import"), source, L"source");
    CZeroValueException<size_t>::Check(NATIVE_SCENE(L"Import"), source_capacity, L"source_capacity");

    if (_import_rule == BUFFER_IMPORT_RULE_THROW) {
        CExceedMemoryException::Check(NATIVE_SCENE(L"Import") ,source_capacity ,_capacity ,L"source_capacity");
    }

    try {
        size_t actualImportedSize = 0;
        if (source_capacity > _capacity) {
            if (_import_rule == BUFFER_IMPORT_RULE_DISCARD) {
                actualImportedSize = _capacity;
            } else {
                _Reallocte(source_capacity);
                actualImportedSize = source_capacity;
            }
        } else {
            actualImportedSize = source_capacity;
        }

        Clear();

        memcpy(_data, source, actualImportedSize);
        _cursor += actualImportedSize;


        return actualImportedSize;
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"Import", e);
    }
}

size_t CBuffer::Import(const CBuffer& source) throw()
{
    try {
        return Import(source.Pointer(), source.Usage());
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"Import", e);
    }
}

size_t CBuffer::Export(pointer_t const desination, size_t desination_capacity) const throw ()
{
    CNullPointerException::Check(NATIVE_SCENE(L"Export"), desination, L"desination");
    CZeroValueException<size_t>::Check(NATIVE_SCENE(L"Export"), desination_capacity, L"desination_capacity");

    size_t actualExportedSize = min(desination_capacity, Usage());
    memcpy(desination, _data, actualExportedSize);

    return actualExportedSize;
}

C_BUFFER& CBuffer::Append(pointer_t const source, size_t source_capacity) throw()
{
    CNullPointerException::Check(NATIVE_SCENE(L"Append"), source, L"source");
    CZeroValueException<size_t>::Check(NATIVE_SCENE(L"Append"), source_capacity, L"source_capacity");

    try {
        if (Remain() > source_capacity) {
            CBuffer temp(*this);

            _Reallocte(Capacity() + source_capacity); 
            Clear();
            Import(temp.Pointer(), temp.Usage());
        }

        memcpy(_data + _cursor, source, source_capacity);
        _cursor += source_capacity;
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"Append", e);
    }
    
    return *this;
}

C_BUFFER& CBuffer::Append(byte_t byte) throw()
{
    try {
        Append(&byte, 1);
    } catch (CException& e) {
        NATIVE_THROW_CAUGHT(L"Append", e);
    }

    return *this;
}

const pointer_t CBuffer::Pointer() const
{
    return _data;
}

C_BUFFER& CBuffer::operator=(C_BUFFER& rvalue) throw()
{
    _Deallocate();

    _capacity = rvalue._capacity;
    _unit_capacity = rvalue._unit_capacity;
    _import_rule = rvalue._import_rule;

    _Allocate(_capacity);
    Clear();

    if (rvalue.Usage() > 0) {
        memcpy(_data, rvalue.Pointer(), rvalue.Usage());
        _cursor += rvalue.Usage();
    }

    return *this;
}